#include "IMultiplex.h"
#include <assert.h>
#include "Channel.h"
#include "Log.h"

namespace dsth {

class DSTHSelect : public IMultiplex {
public:
    DSTHSelect() {}
    virtual ~DSTHSelect() {}
    virtual void UpdateChannel(Channel* channel);
    virtual void RemoveChannel(Channel* channel);
    virtual Timestamp Run(int time_out, ChannelList* active_list);
private:
    // Can't invoke copy constructor or assignment operator
    DSTHSelect(const DSTHSelect&);
    void operator=(const DSTHSelect&);
    
    unsigned int GetSets();
    void DispatchEvent(ChannelList* active_list,
        const fd_set* rfds, const fd_set* wfds, const fd_set* efds);
    void DispatchEvent(const fd_set* fds, int event, ChannelList* active_list);
    
    fd_set rfds_;
    fd_set wfds_;
    fd_set efds_;
};

IMultiplex* CreateMultiplexor() {
    return new DSTHSelect;
}

Timestamp DSTHSelect::Run(int time_out, ChannelList* active_list) {
    if (0 == GetSets()) {
        // return if no socket in this reactor.
        ::Sleep(1000);
        return Timestamp();
    }

    // Must set a timeout for select. Because there is only rfds in select and
	// select waits indefinitely, trying to set wfds to select is inefficacy.
    struct timeval timeout;
	if (time_out == -1) {
		timeout.tv_sec = 0;
		timeout.tv_usec = 500; // 500us
	} else {
		timeout.tv_sec = time_out / 1000;
		timeout.tv_usec = time_out % 1000 * 1000;
	}

    int ret = ::select(0, &rfds_, &wfds_, &efds_, &timeout);
    Timestamp now_time(Timestamp::GetNowTime());
    if (ret == SOCKET_ERROR ) {
        LOG_ERROR("[DSTHSelect::Run] select failed. errorcode:%d\n", ::WSAGetLastError());
        assert(false);
    }
    else {
        DispatchEvent(active_list, &rfds_, &wfds_, &efds_);
    }

    return now_time;
}

unsigned int DSTHSelect::GetSets() {
    FD_ZERO(&rfds_);
	FD_ZERO(&wfds_);
	FD_ZERO(&efds_);
    LOG_TRACE("[DSTHSelect::GetSets] channel map size:%d.", channel_map_.size());
    for (ChannelMap::iterator it = channel_map_.begin();
        it != channel_map_.end(); ++it) {
        Channel* channel = it->second;
        int fd = channel->GetSocketFd();
        if (channel->IsReading()) {
            FD_SET(fd, &rfds_);
            LOG_TRACE("[DSTHSelect::GetSets] channel is reading, socket fd:%d.", fd);
        }
        if (channel->IsWriting()) {
            FD_SET(fd, &wfds_);
            LOG_TRACE("[DSTHSelect::GetSets] channel is writing, socket fd:%d.", fd);
        }
        // TODO: error events.
    }
    
    return rfds_.fd_count + wfds_.fd_count;
}

void DSTHSelect::DispatchEvent(ChannelList* active_list,
        const fd_set* rfds, const fd_set* wfds, const fd_set* efds) {
    if (rfds) DispatchEvent(rfds, Channel::kReadEvent, active_list);
    if (wfds) DispatchEvent(wfds, Channel::kWriteEvent, active_list);
    //if (efds) DispatchEvent(efds, 0x04);
}

void DSTHSelect::DispatchEvent(const fd_set* fds, int event, ChannelList* active_list) {
    for (unsigned int i = 0; i < fds->fd_count; ++i) {
        int fd = static_cast<int>(fds->fd_array[i]);
        ChannelMap::iterator it = channel_map_.find(fd);
        assert(it != channel_map_.end());
        Channel* channel = it->second;
        assert(channel->GetSocketFd() == fd);
        channel->SetRevent(event);
        active_list->push_back(channel);
    }
}

void DSTHSelect::UpdateChannel(Channel* channel) {
    assert(channel != NULL);
    int fd = channel->GetSocketFd();
    LOG_TRACE("[DSTHSelect::UpdateChannel] channel event:%d.", channel->GetEvent());
    if (channel_map_.find(fd) == channel_map_.end()) {
        // add a new one
        channel_map_[fd] = channel;
        LOG_DEBUG("[DSTHSelect::UpdateChannel] Add a new channel, socket fd:%d.", fd);
    } else {
        // update a exist one
        if (channel->IsNoneEvent()) {
            channel_map_.erase(fd);
            LOG_DEBUG("[DSTHSelect::UpdateChannel] Channel is none event and erased, socket fd:%d.", fd);
        } else {
            channel_map_[fd] = channel;
            LOG_DEBUG("[DSTHSelect::UpdateChannel] Update a exist channel, socket fd:%d.", fd);
        }
    }
}

void DSTHSelect::RemoveChannel(Channel* channel) {
    assert(channel != NULL);
    channel_map_.erase(channel->GetSocketFd());
    LOG_DEBUG("[DSTHSelect::RemoveChannel] socket fd:%d.", channel->GetSocketFd());
}

} // namespace
